#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

uint32_t toBigEndian_word(uint32_t num)
{
    uint32_t b0 = (num & 0x000000ff) << 24u;
    uint32_t b1 = (num & 0x0000ff00) << 8u;
    uint32_t b2 = (num & 0x00ff0000) >> 8u;
    uint32_t b3 = (num & 0xff000000) >> 24u;

    uint32_t res = b0 | b1 | b2 | b3;
    return res;
}

uint16_t toBigEndian_halfword(uint16_t num)
{
    uint16_t b0 = (num & 0x000000ff) << 8u;
    uint16_t b1 = (num & 0x0000ff00) >> 8u;

    uint16_t res = b0 | b1;
    return res;
}
int lw(FILE * stream, int address, short offset)
{
    fseek(stream, address + offset, SEEK_SET);
    int res;
    fread(&res, 1, 4, stream);
    return res;
}

unsigned char lbu(FILE * stream, int address, short offset)
{
    fseek(stream, address + offset, SEEK_SET);
    unsigned char res;
    fread(&res, 1, 1, stream);
    return res;
}

unsigned short lhu(FILE * stream, int address, short offset)
{
    fseek(stream, address + offset, SEEK_SET);
    unsigned short res;
    fread(&res, 1, 2, stream);
    return res;
}

void sw(FILE *stream, int address, short offset, int value)
{
    fseek(stream, address + offset, SEEK_SET);
    fwrite(&value, 1, 4, stream);
}

void sb(FILE *stream, int address, short offset, char value)
{
    fseek(stream, address + offset, SEEK_SET);
    fwrite(&value, 1, 1, stream);
}

void process_POF0(FILE * f, char filename[], int POF0_offset=0x1D2E0, int POF0_size=0x0288, bool is_little=true)
{
    int at;
    int v1;
    int a0=POF0_offset; // The Rock's POF0
    int a1=POF0_size; // Size of POF0
    int a2=0x08; // YOBJ size + 4
    int a3; // Start of POF0 String
    int t0;
    int t1;
    int t2;
    int t3;
    int t4;

    FILE * yobj_file = f;
    char new_filename[255];
    strcpy(new_filename, filename);
    strcat(new_filename, ".decrypted");
    FILE * output_file = fopen(new_filename, "wb+");

    // Get size of yobj
    fseek(yobj_file, 0L, SEEK_END);
    int yobj_size = ftell(yobj_file);

    char * buffer = (char *) malloc(yobj_size);
    rewind(yobj_file);
    fread(buffer, 1, yobj_size, yobj_file);

    fseek(output_file, 0L, SEEK_SET);

    fwrite(buffer, 1, yobj_size, output_file);

    free(buffer);

    while (true)
    {
        t0 = a0 + a1;
        at = a0 < t0;
        if (!at)
            break;
        t1 = a2;
        a1 = 0x80;
        v1 = 0x40;
        a3 = 0xC0;
        t3 = lbu(output_file, a0, 0x00);
        while (true)
        {
            //printf("Offset: %.8X\n", a0);
            t2 = t3 & 0xC0;
            //printf("%.8X %.2X\n", a0, t3);
            if (t2 == a3)
            {
                unsigned short sp;
                sp = 0;
                //*(i+3) =t3;
                sp |= (t3 & 0xFF) << 24;
                // case 3
                t4 = lbu(output_file, a0, 0x03);
                t3 = lbu(output_file, a0, 0x02);
                t2 = lbu(output_file, a0, 0x01);

                //*(i+0)= t4;
                //*(i+1)= t3;
                //*(i+2)= t2;
                sp |= t4 & 0xFF;
                sp |= (t3 & 0xFF) << 8;
                sp |= (t2 & 0xFF) << 16;
                t2 = sp;

                if (!is_little)
                    t2 = toBigEndian_word(t2);
                a0 += 4;
                t2 <<= 2;
                t2 >>= 2;
                t2 <<= 2;
                a2 += t2;
                t2 = lw(output_file, a2, 0x00);
                if (!is_little)
                    t2 = toBigEndian_word(t2);
                t2 += t1;
                // little Endian
                if (!is_little)
                    t2 = toBigEndian_word(t2);
                sw(output_file, a2, 0x00, t2);
                if (!is_little)
                    t2 = toBigEndian_word(t2);
            }
            else if (t2 == a1)
            {
                // case 2
                t2 = lbu(output_file, a0, 0x01);
                unsigned short  sp;
                sp = 0;
                //*(i+1)=t3;
                //*(i+0)=t2;
                sp |= (t3 & 0xFF) << 8;
                sp |= t2 & 0xFF;
                t2 = sp;
                if (!is_little)
                    t2 = toBigEndian_halfword(t2);

                a0 += 2;
                t2 &= 0x3FFF;
                t2 <<= 2;
                a2 += t2;
                t2 = lw(output_file, a2, 0x00);
                if (!is_little)
                    t2 = toBigEndian_word(t2);
                t2 += t1;
                // little Endian
                if (!is_little)
                    t2 = toBigEndian_word(t2);
                sw(output_file, a2, 0x00, t2);
                if (!is_little)
                    t2 = toBigEndian_word(t2);
            }
            else if (t2 == v1)
            {
                // case 1

                t2 = t3 & 0x3F;

                t2 <<= 2;


                a2 += t2;

                t2 = lw(output_file, a2, 0x00);
                if (!is_little)
                    t2 = toBigEndian_word(t2);
                //printf("%.8X\n", t2);
                a0 += 1;
                t2 += t1;

                if (!is_little)
                    t2 = toBigEndian_word(t2);

                sw(output_file, a2, 0x00, t2);
                if (!is_little)
                    t2 = toBigEndian_word(t2);
            }
            else
                a0 += 1;

            // 0x0890D2B8
            t2 = a0 < t0;
            if (t2)
            {
                t3 = lbu(output_file, a0, 0x0);
                continue;
            }
            break;
        }
        break;
    }


    fclose(output_file);
    return;

}
int main(int argc, char * argv[])
{
    char filename[255];
    int POF0_offset;
    int POF0_size;
    char byteorder[8];
    printf("POF0 decoder by eatrawmeat391\n");

    if (argc != 4)
    {
        printf("Usage: POF0.exe infile POF0_offset POF0_size");
        return 0;
    }

    strcpy(filename, argv[1]);
    POF0_offset = (int) strtol(argv[2], NULL, 16);
    POF0_size   = (int) strtol(argv[3], NULL, 16);
    //strcpy(byteorder, argv[4]);
    bool is_little;
    //if (strcmpi(byteorder, "little") == 0)
        //is_little = true;
    //else
        //is_little = false;
    is_little = true;
    FILE * yobj_file = fopen(filename, "rb");
    if (!yobj_file)
    {
        printf("Cannot open this file.\n");
        return 0;
    }
    process_POF0(yobj_file, filename, POF0_offset, POF0_size, is_little);
    fclose(yobj_file);

    printf("POF0 successfully decoded, the file %s.decrypted has been created.\n");
    getchar();
    return 0;
}
